Skip to content

feat(shared,ui): Render plan badge in OrganizationSwitcher#9149

Open
dstaley wants to merge 6 commits into
mainfrom
ds.feat/billing-plan-name
Open

feat(shared,ui): Render plan badge in OrganizationSwitcher#9149
dstaley wants to merge 6 commits into
mainfrom
ds.feat/billing-plan-name

Conversation

@dstaley

@dstaley dstaley commented Jul 13, 2026

Copy link
Copy Markdown
Member

Description

This PR adds a new prop to the OrganizationSwitcher component, showPlanName, that renders a badge when the current active organization is subscribed to a Clerk Billing Plan. The badge has an animated loading state (since it's resolved separately from the Organization information). Free plans use the primary color scheme, whereas paid plans use the secondary. This gives paid plans more visual weight.

Dark Mode (Paid) Light Mode (Paid)
CleanShot 2026-07-13 at 14 48 35@2x CleanShot 2026-07-13 at 14 49 09@2x
Dark Mode (Free) Light Mode (Free)
CleanShot 2026-07-13 at 14 51 21@2x CleanShot 2026-07-13 at 14 50 57@2x
Dark Mode (Loading) Light Mode (Loading)
CleanShot 2026-07-13 at 14 57 30@2x CleanShot 2026-07-13 at 14 49 51@2x

Checklist

  • pnpm test runs as expected.
  • pnpm build runs as expected.
  • (If applicable) JSDoc comments have been added or updated for any package exports
  • (If applicable) Documentation has been updated

Type of change

  • 🐛 Bug fix
  • 🌟 New feature
  • 🔨 Breaking change
  • 📖 Refactoring / dependency upgrade / documentation
  • other:

Summary by CodeRabbit

  • New Features
    • Added an option (showPlanName) to display the active organization’s Clerk Billing plan name in the Organization Switcher.
    • Supports both boolean and async badge rendering, including a loading badge while plan details are fetched.
    • Added appearance customization for the plan badge on the Organization Switcher trigger.
  • Style
    • Introduced a subtle pulse animation for loading feedback.
  • Bug Fixes
    • Plan badge now renders only when enabled by billing settings and when a relevant subscription is available.
  • Tests
    • Added coverage to verify the badge is shown/hidden based on billing configuration.

@dstaley
dstaley requested a review from alexcarpenter July 13, 2026 19:58
@vercel

vercel Bot commented Jul 13, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
clerk-js-sandbox Ready Ready Preview, Comment Jul 17, 2026 5:20pm
swingset Ready Ready Preview, Comment Jul 17, 2026 5:20pm

Request Review

@changeset-bot

changeset-bot Bot commented Jul 13, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 1a38fc1

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 23 packages
Name Type
@clerk/shared Minor
@clerk/ui Minor
@clerk/astro Patch
@clerk/backend Patch
@clerk/chrome-extension Patch
@clerk/clerk-js Patch
@clerk/electron Patch
@clerk/expo-passkeys Patch
@clerk/expo Patch
@clerk/express Patch
@clerk/fastify Patch
@clerk/headless Patch
@clerk/hono Patch
@clerk/localizations Patch
@clerk/msw Patch
@clerk/nextjs Patch
@clerk/nuxt Patch
@clerk/react-router Patch
@clerk/react Patch
@clerk/tanstack-react-start Patch
@clerk/testing Patch
@clerk/vue Patch
@clerk/swingset Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@github-actions github-actions Bot added the ui label Jul 13, 2026
)}
</AcceptedInvitationsProvider>
</Flow.Root>
<SubscriberTypeContext.Provider value='organization'>

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the only thing changed here is wrapping the existing Flow.Root in the SubscriberTypeContext.Provider

@coderabbitai

coderabbitai Bot commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

OrganizationSwitcher can optionally display the active organization’s Billing plan name. The change adds subscription-aware and custom badge rendering, loading styling, appearance customization, organization subscriber context, tests, and release metadata.

Changes

Organization plan badge

Layer / File(s) Summary
Plan display contract and subscriber context
packages/shared/src/types/clerk.ts, packages/ui/src/components/OrganizationSwitcher/index.tsx
Adds the optional renderPlanBadge prop and provides organization subscriber context to the switcher subtree.
Subscription badge rendering and styling
packages/ui/src/components/OrganizationSwitcher/OrganizationSwitcherTrigger.tsx, packages/ui/src/elements/OrganizationPreview.tsx, packages/ui/src/elements/Badge.tsx, packages/ui/src/styledSystem/animations.ts, packages/ui/src/customizables/elementDescriptors.ts, packages/ui/src/internal/appearance.ts
Renders loading, subscription, or custom plan badges, separates the badge from the organization name layout, adds pulse animation support, and exposes badge appearance customization.
Behavior tests and release metadata
packages/ui/src/components/OrganizationSwitcher/__tests__/OrganizationSwitcher.test.tsx, .changeset/hungry-lands-fold.md
Tests enabled and disabled organization billing behavior and records minor package releases and the documented property.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant OrganizationSwitcherTrigger
  participant useSubscription
  participant Billing
  participant OrganizationPreview
  participant Badge
  OrganizationSwitcherTrigger->>useSubscription: Read active organization subscription
  useSubscription->>Billing: Fetch subscription
  Billing-->>useSubscription: Return subscription items
  useSubscription-->>OrganizationSwitcherTrigger: Loading or plan item state
  OrganizationSwitcherTrigger->>OrganizationPreview: Pass plan badge
  OrganizationPreview->>Badge: Render loading or plan badge
Loading

Suggested reviewers: alexcarpenter, jacekradko

Poem

A bunny hops through plans so bright,
A badge appears in silver light.
It pulses softly while things load,
Then names the plan along the road.
“Pro Plan!” says the rabbit, “off we go!”

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: rendering a plan badge in the OrganizationSwitcher.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch

Comment @coderabbitai help to get the list of available commands.


if (isLoading) {
// 5ch is specifically chosen to balance the size of "Free" versus paid plans in Inter
return <LoadingBadge sx={{ width: '5ch' }} />;

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the screenshots in the PR make this look larger than it actually is. This is what it looks like when not zoomed in.

Image

return (
<Badge
elementDescriptor={descriptors.organizationSwitcherTriggerBadge}
elementId={descriptors.organizationSwitcherTriggerBadge.setId(slug)}

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

added this so you can specifically target certain plans to customize the badge

@pkg-pr-new

pkg-pr-new Bot commented Jul 13, 2026

Copy link
Copy Markdown

Open in StackBlitz

@clerk/astro

npm i https://pkg.pr.new/@clerk/astro@9149

@clerk/backend

npm i https://pkg.pr.new/@clerk/backend@9149

@clerk/chrome-extension

npm i https://pkg.pr.new/@clerk/chrome-extension@9149

@clerk/clerk-js

npm i https://pkg.pr.new/@clerk/clerk-js@9149

@clerk/electron

npm i https://pkg.pr.new/@clerk/electron@9149

@clerk/electron-passkeys

npm i https://pkg.pr.new/@clerk/electron-passkeys@9149

@clerk/eslint-plugin

npm i https://pkg.pr.new/@clerk/eslint-plugin@9149

@clerk/expo

npm i https://pkg.pr.new/@clerk/expo@9149

@clerk/expo-passkeys

npm i https://pkg.pr.new/@clerk/expo-passkeys@9149

@clerk/express

npm i https://pkg.pr.new/@clerk/express@9149

@clerk/fastify

npm i https://pkg.pr.new/@clerk/fastify@9149

@clerk/hono

npm i https://pkg.pr.new/@clerk/hono@9149

@clerk/localizations

npm i https://pkg.pr.new/@clerk/localizations@9149

@clerk/nextjs

npm i https://pkg.pr.new/@clerk/nextjs@9149

@clerk/nuxt

npm i https://pkg.pr.new/@clerk/nuxt@9149

@clerk/react

npm i https://pkg.pr.new/@clerk/react@9149

@clerk/react-router

npm i https://pkg.pr.new/@clerk/react-router@9149

@clerk/shared

npm i https://pkg.pr.new/@clerk/shared@9149

@clerk/tanstack-react-start

npm i https://pkg.pr.new/@clerk/tanstack-react-start@9149

@clerk/testing

npm i https://pkg.pr.new/@clerk/testing@9149

@clerk/ui

npm i https://pkg.pr.new/@clerk/ui@9149

@clerk/upgrade

npm i https://pkg.pr.new/@clerk/upgrade@9149

@clerk/vue

npm i https://pkg.pr.new/@clerk/vue@9149

commit: 1a38fc1

@github-actions

github-actions Bot commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

API Changes Report

Generated by Break Check on 2026-07-17T17:22:17.008Z

Summary

Metric Count
Packages analyzed 19
Packages with changes 2
🔴 Breaking changes 0
🟡 Non-breaking changes 2
🟢 Additions 0

🤖 This report was reviewed by claude-sonnet-4-6.


@clerk/shared

Current version: 4.25.2
Recommended bump: MINOR → 4.26.0

Subpath ./types

🟡 Non-breaking Changes (1)

Modified: OrganizationSwitcherProps
// ... 8 unchanged lines elided ...
    skipInvitationScreen?: boolean;
    appearance?: ClerkAppearanceTheme;
    organizationProfileProps?: Pick<OrganizationProfileProps, 'appearance' | 'customPages'>;
+   renderPlanBadge?: boolean | (() => Promise<{
+     label: string;
+     slug: string;
+     colorScheme?: 'primary' | 'secondary';
+   }>);
  };

Static analyzer: Breaking change in type alias OrganizationSwitcherProps: Type changed: import("@clerk/shared").~CreateOrganizationMode&import("@clerk/shared").~OrganizationProfileMode&{defaultOpen?:boolean;…import("@clerk/shared").~CreateOrganizationMode&import("@clerk/shared").~OrganizationProfileMode&{defaultOpen?:boolean;…

🤖 AI review (reclassified as non-breaking) (95%): The change adds a new optional property renderPlanBadge to OrganizationSwitcherProps, which is used as an input parameter to mountOrganizationSwitcher. Adding a new optional property to an input type is non-breaking: existing callers that don't supply it remain valid, and they are not required to construct the type exhaustively.


@clerk/ui

Current version: 1.25.2
Recommended bump: MINOR → 1.26.0

Subpath ./internal

🟡 Non-breaking Changes (1)

Modified: ElementsConfig
// ... 170 unchanged lines elided ...
    userButtonPopoverFooterPagesLink: WithOptions<'terms' | 'privacy'>;
    organizationSwitcherTrigger: WithOptions<'personal' | 'organization', 'open'>;
    organizationSwitcherTriggerIcon: WithOptions<never, 'open'>;
+   organizationSwitcherTriggerBadge: WithOptions<string>;
    organizationSwitcherPopoverRootBox: WithOptions;
    organizationSwitcherPopoverCard: WithOptions;
    organizationSwitcherPopoverMain: WithOptions;
// ... 379 unchanged lines elided ...

Static analyzer: Breaking change in type alias ElementsConfig: Type changed: {button:import("@clerk/ui").~WithOptions<string>;input:import("@clerk/ui").~WithOptions;checkbox:import("@clerk/ui").~W…{button:import("@clerk/ui").~WithOptions<string>;input:import("@clerk/ui").~WithOptions;checkbox:import("@clerk/ui").~W…

🤖 AI review (reclassified as non-breaking) (85%): The diff shows one additional line in the elided section (475 → 476 lines), indicating a new property was added to ElementsConfig; ElementsConfig is used only as a mapped-type key source for the output type Elements, so consumers only read derived values and are not required to construct ElementsConfig objects directly, making the addition non-breaking.


Report generated by Break Check

Last ran on 1a38fc1.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
packages/ui/src/components/OrganizationSwitcher/__tests__/OrganizationSwitcher.test.tsx (1)

90-166: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Missing coverage for the free-plan (primary) and loading-state paths.

Both new tests only cover a paid plan (isDefault: falsesecondary colorScheme). Per the PR objective, free plans should render with the primary color scheme, and there's also an animated loading state (LoadingBadge) rendered while the subscription is being resolved — neither is exercised here. As per path instructions, **/*.{test,spec}.{ts,tsx,js,jsx} requires that "Unit tests are required for all new functionality" and to "Verify proper error handling and edge cases."

Consider adding:

  • A test with isDefault: true asserting the primary colorScheme is applied.
  • A test asserting the pulsing LoadingBadge renders while getSubscription is pending.

Do you want me to draft these additional test cases?

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@packages/ui/src/components/OrganizationSwitcher/__tests__/OrganizationSwitcher.test.tsx`
around lines 90 - 166, Add coverage in OrganizationSwitcher tests for both
missing paths: configure the mocked subscription plan with isDefault: true and
assert the rendered plan badge uses the primary color scheme, then keep
getSubscription pending and assert the animated LoadingBadge appears during
resolution. Reuse the existing fixtures and showPlanName setup while preserving
the paid-plan and billing-disabled tests.

Source: Path instructions

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@packages/ui/src/components/OrganizationSwitcher/OrganizationSwitcherTrigger.tsx`:
- Line 66: Update OrganizationSwitcherTrigger’s badge rendering and the
corresponding PlanBadge usage around the noted lines so PlanBadge is mounted
only when showPlanName is enabled. Ensure useSubscription() is not invoked on
the default showPlanName={false} path, while preserving the existing badge
behavior when showPlanName is true.
- Around line 106-108: Update the LoadingBadge render in
OrganizationSwitcherTrigger to apply the same organizationSwitcherTriggerBadge
descriptor and corresponding elementId used by the normal plan badge, while
preserving its loading width and existing behavior.

---

Nitpick comments:
In
`@packages/ui/src/components/OrganizationSwitcher/__tests__/OrganizationSwitcher.test.tsx`:
- Around line 90-166: Add coverage in OrganizationSwitcher tests for both
missing paths: configure the mocked subscription plan with isDefault: true and
assert the rendered plan badge uses the primary color scheme, then keep
getSubscription pending and assert the animated LoadingBadge appears during
resolution. Reuse the existing fixtures and showPlanName setup while preserving
the paid-plan and billing-disabled tests.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository YAML (base), Repository UI (inherited)

Review profile: CHILL

Plan: Pro Plus

Run ID: beb9aa54-c754-49fd-a67d-36e1d6dce633

📥 Commits

Reviewing files that changed from the base of the PR and between ea75149 and 393cb2b.

📒 Files selected for processing (9)
  • .changeset/hungry-lands-fold.md
  • packages/shared/src/types/clerk.ts
  • packages/ui/src/components/OrganizationSwitcher/OrganizationSwitcherTrigger.tsx
  • packages/ui/src/components/OrganizationSwitcher/__tests__/OrganizationSwitcher.test.tsx
  • packages/ui/src/components/OrganizationSwitcher/index.tsx
  • packages/ui/src/customizables/elementDescriptors.ts
  • packages/ui/src/elements/Badge.tsx
  • packages/ui/src/internal/appearance.ts
  • packages/ui/src/styledSystem/animations.ts

Comment on lines +106 to +108
if (isLoading) {
// 5ch is specifically chosen to balance the size of "Free" versus paid plans in Inter
return <LoadingBadge sx={{ width: '5ch' }} />;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Apply the OrganizationSwitcher badge descriptor to the loading state.

The normal plan badge uses organizationSwitcherTriggerBadge, but LoadingBadge is rendered without an elementDescriptor or elementId. Appearance overrides therefore cannot target the loading placeholder. As per path instructions, themable elements under packages/ui/src must apply element descriptors.

Suggested fix
-    return <LoadingBadge sx={{ width: '5ch' }} />;
+    return (
+      <LoadingBadge
+        elementDescriptor={descriptors.organizationSwitcherTriggerBadge}
+        elementId={descriptors.organizationSwitcherTriggerBadge.setId('loading')}
+        sx={{ width: '5ch' }}
+      />
+    );
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (isLoading) {
// 5ch is specifically chosen to balance the size of "Free" versus paid plans in Inter
return <LoadingBadge sx={{ width: '5ch' }} />;
if (isLoading) {
// 5ch is specifically chosen to balance the size of "Free" versus paid plans in Inter
return (
<LoadingBadge
elementDescriptor={descriptors.organizationSwitcherTriggerBadge}
elementId={descriptors.organizationSwitcherTriggerBadge.setId('loading')}
sx={{ width: '5ch' }}
/>
);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@packages/ui/src/components/OrganizationSwitcher/OrganizationSwitcherTrigger.tsx`
around lines 106 - 108, Update the LoadingBadge render in
OrganizationSwitcherTrigger to apply the same organizationSwitcherTriggerBadge
descriptor and corresponding elementId used by the normal plan badge, while
preserving its loading width and existing behavior.

Source: Path instructions

@alexcarpenter alexcarpenter left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you add an example showing how this wraps?

colorScheme={isDefault ? 'primary' : 'secondary'}
sx={{ whiteSpace: 'nowrap' }}
>
<Text

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we need the extra text usage in the badge vs supplying the sizing to the badge component?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nah, we use <Text> in Badge in a few places, but here we don't really need it. Removed!

@dstaley

dstaley commented Jul 13, 2026

Copy link
Copy Markdown
Member Author

@alexcarpenter played around a bit and this is where I landed

with a long organization name:
CleanShot 2026-07-13 at 16 26 33@2x

with a long plan name:
CleanShot 2026-07-13 at 16 25 57@2x

with long organization and long plan name:
CleanShot 2026-07-13 at 16 24 43@2x

{organization.name} {badge}
<Box
as='span'
sx={{ flex: '1 1 auto', minWidth: 0, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if the org name should include a longer min width to ensure it never is completely hidden with a long plan name. not sure if you tried that combo.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The styling is somewhat confusing, but the general structure is that the parent element has max-width: 30ch, and the badge has max-width: 14ch, so the badge will only ever take up slightly less than half of the total space. The min-width rule here is to allow for a flex item to be narrower than its intrinsic width. From testing I don't think the min-width actually has any impact on the minimum width the organization name has to render within (which should always be 16ch).

@macroscopeapp

macroscopeapp Bot commented Jul 17, 2026

Copy link
Copy Markdown

Approvability

Verdict: Needs human review

This PR introduces a new user-facing feature (plan badge display) with billing system integration. Additionally, there's an unresolved review comment about unnecessary subscription fetches when the feature is disabled, which affects runtime behavior.

You can customize Macroscope's approvability policy. Learn more.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
packages/shared/src/types/clerk.ts (1)

2229-2234: 📐 Maintainability & Code Quality | 🟠 Major | 🏗️ Heavy lift

JSDoc for renderPlanBadge doesn't document the custom function option.

The JSDoc only describes the boolean behavior ("Shows the Clerk Billing Plan name") but the prop also accepts an async function for custom badge rendering. This is a public/reference-facing API that may render in generated Clerk Docs, so the JSDoc should accurately describe both modes, including the function's return shape ({ label, slug, colorScheme? }).

As per path instructions, JSDoc on public/reference-facing APIs is customer-facing documentation. The Docs team may need to review this change.

📝 Proposed JSDoc update
     /**
-     * Shows the Clerk Billing Plan name for the currently active organization.
+     * Controls the plan badge displayed in the OrganizationSwitcher trigger.
+     *
+     * - When `true`, renders the active organization's Clerk Billing plan name.
+     *   Free plans use the `primary` color scheme; paid plans use `secondary`.
+     * - When a function is provided, it is called asynchronously to resolve
+     *   custom badge data: `{ label, slug, colorScheme? }`.
+     * - When `false` or omitted, no badge is rendered.
      *
      * `@default` false
      */
     renderPlanBadge?: boolean | (() => Promise<{ label: string; slug: string; colorScheme?: 'primary' | 'secondary' }>);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/shared/src/types/clerk.ts` around lines 2229 - 2234, Update the
JSDoc for the public renderPlanBadge property to document both supported modes:
the boolean behavior for showing the active organization’s billing plan name and
the async function option for custom badge data. Describe that the function
returns an object containing label and slug, with optional colorScheme limited
to primary or secondary; retain the existing default annotation.

Source: Path instructions

packages/ui/src/components/OrganizationSwitcher/OrganizationSwitcherTrigger.tsx (1)

129-129: 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

LoadingBadge instances lack element descriptors for theming. Both loading-state badges render without elementDescriptor or elementId, so appearance overrides cannot target the loading placeholder. The shared root cause is one missing descriptor applied to both LoadingBadge usages.

  • packages/ui/src/components/OrganizationSwitcher/OrganizationSwitcherTrigger.tsx#L129: Add elementDescriptor={descriptors.organizationSwitcherTriggerBadge} and elementId={descriptors.organizationSwitcherTriggerBadge.setId('loading')} to the LoadingBadge in ClerkBillingPlanBadge.
  • packages/ui/src/components/OrganizationSwitcher/OrganizationSwitcherTrigger.tsx#L170: Apply the same elementDescriptor and elementId to the LoadingBadge in CustomRenderPlanBadge.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@packages/ui/src/components/OrganizationSwitcher/OrganizationSwitcherTrigger.tsx`
at line 129, Update both LoadingBadge usages in ClerkBillingPlanBadge at
packages/ui/src/components/OrganizationSwitcher/OrganizationSwitcherTrigger.tsx#L129
and CustomRenderPlanBadge at
packages/ui/src/components/OrganizationSwitcher/OrganizationSwitcherTrigger.tsx#L170
to include descriptors.organizationSwitcherTriggerBadge as elementDescriptor and
its setId('loading') result as elementId.

Source: Path instructions

🧹 Nitpick comments (2)
packages/shared/src/types/clerk.ts (1)

2234-2234: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consider extracting the inline function type for reuse.

The return type () => Promise<{ label: string; slug: string; colorScheme?: 'primary' | 'secondary' }> is duplicated verbatim in OrganizationSwitcherTrigger.tsx (line 151). Extracting a named type (e.g., RenderPlanBadgeResult) would keep the contract in sync if the shape changes.

♻️ Proposed type extraction
+export type PlanBadgeData = {
+  label: string;
+  slug: string;
+  colorScheme?: 'primary' | 'secondary';
+};
+
     renderPlanBadge?: boolean | (() => Promise<PlanBadgeData>);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/shared/src/types/clerk.ts` at line 2234, Extract the duplicated
render-plan-badge callback result shape into a named shared type, such as
RenderPlanBadgeResult, in the shared Clerk types. Update the renderPlanBadge
declaration and OrganizationSwitcherTrigger.tsx callback type to reference it,
preserving the existing fields and optional colorScheme contract.

Source: Coding guidelines

packages/ui/src/components/OrganizationSwitcher/OrganizationSwitcherTrigger.tsx (1)

158-167: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Add cleanup to CustomRenderPlanBadge's useEffect.

The async IIFE has no cleanup — if the component unmounts before the promise resolves, setState fires on an unmounted component and the async operation continues unnecessarily. Add an isMounted guard.

🔒 Proposed fix
   useEffect(() => {
+    let isMounted = true;
     (async () => {
       try {
         const data = await renderPlanBadge();
-        setState({ isLoading: false, data });
+        if (isMounted) setState({ isLoading: false, data });
       } catch {
-        setState({ isLoading: false, data: null });
+        if (isMounted) setState({ isLoading: false, data: null });
       }
     })();
+    return () => { isMounted = false; };
   }, [renderPlanBadge]);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@packages/ui/src/components/OrganizationSwitcher/OrganizationSwitcherTrigger.tsx`
around lines 158 - 167, Update the useEffect invoking renderPlanBadge in
CustomRenderPlanBadge with an isMounted guard: set it true before starting the
async operation, check it before both setState calls, and return cleanup that
marks it false. Preserve the existing loading, success, and error state values
while preventing updates after unmount.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@packages/shared/src/types/clerk.ts`:
- Around line 2229-2234: Update the JSDoc for the public renderPlanBadge
property to document both supported modes: the boolean behavior for showing the
active organization’s billing plan name and the async function option for custom
badge data. Describe that the function returns an object containing label and
slug, with optional colorScheme limited to primary or secondary; retain the
existing default annotation.

In
`@packages/ui/src/components/OrganizationSwitcher/OrganizationSwitcherTrigger.tsx`:
- Line 129: Update both LoadingBadge usages in ClerkBillingPlanBadge at
packages/ui/src/components/OrganizationSwitcher/OrganizationSwitcherTrigger.tsx#L129
and CustomRenderPlanBadge at
packages/ui/src/components/OrganizationSwitcher/OrganizationSwitcherTrigger.tsx#L170
to include descriptors.organizationSwitcherTriggerBadge as elementDescriptor and
its setId('loading') result as elementId.

---

Nitpick comments:
In `@packages/shared/src/types/clerk.ts`:
- Line 2234: Extract the duplicated render-plan-badge callback result shape into
a named shared type, such as RenderPlanBadgeResult, in the shared Clerk types.
Update the renderPlanBadge declaration and OrganizationSwitcherTrigger.tsx
callback type to reference it, preserving the existing fields and optional
colorScheme contract.

In
`@packages/ui/src/components/OrganizationSwitcher/OrganizationSwitcherTrigger.tsx`:
- Around line 158-167: Update the useEffect invoking renderPlanBadge in
CustomRenderPlanBadge with an isMounted guard: set it true before starting the
async operation, check it before both setState calls, and return cleanup that
marks it false. Preserve the existing loading, success, and error state values
while preventing updates after unmount.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository YAML (base), Repository UI (inherited)

Review profile: CHILL

Plan: Pro Plus

Run ID: 1ad36dae-eb01-4893-81bc-ff999f5a9e6e

📥 Commits

Reviewing files that changed from the base of the PR and between 6d49e41 and 1a38fc1.

📒 Files selected for processing (3)
  • packages/shared/src/types/clerk.ts
  • packages/ui/src/components/OrganizationSwitcher/OrganizationSwitcherTrigger.tsx
  • packages/ui/src/components/OrganizationSwitcher/__tests__/OrganizationSwitcher.test.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/ui/src/components/OrganizationSwitcher/tests/OrganizationSwitcher.test.tsx

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants